package com.siaron.framework.eventbus.autoconfigure;

import com.siaron.framework.eventbus.Constant;
import com.siaron.framework.eventbus.annotation.*;
import com.siaron.framework.eventbus.binder.BinderPropertiesProcess;
import com.siaron.framework.eventbus.binder.BinderRelation;
import com.siaron.framework.eventbus.model.FunType;
import com.siaron.framework.eventbus.model.FunctionInfo;
import com.siaron.framework.eventbus.utils.BeanNameUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cloud.stream.binder.ConsumerProperties;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author xielongwang
 * @create 2021/7/20 10:43 上午
 * @email siaron.wang@gmail.com
 * @description 扫描所有的事件相关类并缓存起来
 */
public class EventPubSubSelector implements ImportBeanDefinitionRegistrar, EnvironmentAware,
        BeanFactoryAware, ResourceLoaderAware {

    private static final Logger log = LoggerFactory.getLogger(EventPubSubSelector.class);

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;

    private Environment environment;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!isEnabled()) {
            return;
        }
        AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(importingClassMetadata
                .getAnnotationAttributes(EnableEventBus.class.getCanonicalName()));
        if (annotationAttributes == null) {
            log.warn("No EnableEventBus Annotation.");
            return;
        }
        ClassPathPubSubFunctionProvider classPathPubSubFunctionProvider = new ClassPathPubSubFunctionProvider(registry, false);
        classPathPubSubFunctionProvider.registerFilter();
        classPathPubSubFunctionProvider.setResourceLoader(this.resourceLoader);
        Set<BeanDefinitionHolder> beanDefinitionHolders = classPathPubSubFunctionProvider.doScan(annotationAttributes.getStringArray("basePackages"));

        processBeanDefinition(beanDefinitionHolders);
    }

    private void processBeanDefinition(Set<BeanDefinitionHolder> beanDefinitionHolders) {
        Map<String, String> eventKeyInfo = new HashMap<>(16);
        Map<String, FunctionInfo> functionInfo = new HashMap<>(16);
        for (BeanDefinitionHolder bdh : beanDefinitionHolders) {
            ScannedGenericBeanDefinition definition = (ScannedGenericBeanDefinition) bdh.getBeanDefinition();
            AnnotationMetadata annotationMetadata = definition.getMetadata();
            boolean isConfiguration = annotationMetadata.hasAnnotation(Configuration.class.getName());
            if (isConfiguration) {
                configurationProcess(definition, functionInfo);
                continue;
            }

            boolean isEventModel = annotationMetadata.hasAnnotation(Event.class.getName());
            if (isEventModel) {
                eventModelProcess(definition, eventKeyInfo, functionInfo);
                continue;
            }

            boolean isEventPublish = annotationMetadata.hasAnnotation(EventPublish.class.getName());
            if (isEventPublish) {
                eventPublishProcess(definition, functionInfo);
                continue;
            }

            boolean isEventSubscribe = annotationMetadata.hasAnnotation(EventSubscribe.class.getName());
            if (isEventSubscribe) {
                eventSubscribeProcess(definition, functionInfo);
            }
        }

        mergerToCache(eventKeyInfo, functionInfo);
    }

    private void mergerToCache(Map<String, String> eventKeyInfo, Map<String, FunctionInfo> functionInfo) {
        for (Map.Entry<String, FunctionInfo> funInfo : functionInfo.entrySet()) {
            FunctionInfo info = funInfo.getValue();
            String eventRealKey = eventKeyInfo.get(info.getEvent());
            if (org.apache.commons.lang3.StringUtils.isNotBlank(eventRealKey)) {
                info.setEventKey(eventRealKey);
            }
            //生成binder的属性配置
            BinderRelation bindersType = info.getBindersType();
            if (bindersType != null) {
                BinderPropertiesProcess bpp = bindersType.getBinderPropertiesProcess();
                Map<String, Object> binderProperties = bpp.processProperties(funInfo.getKey(), funInfo.getValue());
                info.setBinderBindings(binderProperties);
            }
            BindingCache.getFun().put(funInfo.getKey(), info);
        }
    }

    private void eventSubscribeProcess(ScannedGenericBeanDefinition definition, Map<String, FunctionInfo> functionInfo) {
        Class<?> currentClass = ClassUtils.resolveClassName(definition.getMetadata().getClassName(), null);
        EventSubscribe sub = currentClass.getAnnotation(EventSubscribe.class);
        if (sub == null) {
            return;
        }
        //组件名称
        Component component = currentClass.getAnnotation(Component.class);
        String componentName = component.value();
        if (org.apache.commons.lang3.StringUtils.isBlank(componentName)) {
            componentName = BeanNameUtil.decapitalize(currentClass.getSimpleName());
        }
        //事件
        String event = getEvent(((ParameterizedType) currentClass.getGenericInterfaces()[0]));
        processSubscribe(functionInfo, event, componentName, sub);
    }

    private void eventPublishProcess(ScannedGenericBeanDefinition definition, Map<String, FunctionInfo> functionInfo) {
        Class<?> currentClass = ClassUtils.resolveClassName(definition.getMetadata().getClassName(), null);
        EventPublish sub = currentClass.getAnnotation(EventPublish.class);
        //组件名称
        Component component = currentClass.getAnnotation(Component.class);
        String componentName = component.value();
        if (org.apache.commons.lang3.StringUtils.isBlank(componentName)) {
            componentName = BeanNameUtil.decapitalize(currentClass.getSimpleName());
        }
        //事件
        String event = getEvent(((ParameterizedType) currentClass.getGenericInterfaces()[0]));
        processPublish(functionInfo, event, componentName, sub);
    }

    private void eventModelProcess(ScannedGenericBeanDefinition definition, Map<String, String> eventKeyInfo, Map<String, FunctionInfo> functionInfo) {
        Map<String, Object> annotationAttributes = definition.getMetadata().getAnnotationAttributes(Event.class.getName());
        if (CollectionUtils.isEmpty(annotationAttributes)) {
            return;
        }
        String eventUnionKey = String.valueOf(annotationAttributes.get("eventKey"));
        String className = definition.getMetadata().getClassName();
        if (org.apache.commons.lang3.StringUtils.isBlank(eventUnionKey)) {
            eventUnionKey = className;
        }
        eventKeyInfo.put(className, eventUnionKey);
        String bindingName = className.substring(className.lastIndexOf(".") + 1);
        String[] binder = (String[]) annotationAttributes.get("binder");
        for (String bd : binder) {
            FunctionInfo info = new FunctionInfo();
            info.setType(FunType.OUT);
            BindingProperties bindingProperties = new BindingProperties();
            bindingProperties.setDestination(Constant.EVENTBUS);
            bindingProperties.setBinder(bd);
            info.setBindersType(getBinderRelation(bd));
            info.setEventKey(eventUnionKey);
            info.setBindingProperties(bindingProperties);
            functionInfo.put(bindingName + Constant.UNDERSCORE + bd + Constant.OUT_SUFFIX, info);
        }
    }

    private BinderRelation getBinderRelation(String bd) {
        return BinderRelation.valueOf(environment.getProperty("spring.cloud.stream.binders." + bd + ".type"));
    }

    private void configurationProcess(ScannedGenericBeanDefinition definition, Map<String, FunctionInfo> functionInfo) {
        Class<?> currentClass = ClassUtils.resolveClassName(definition.getMetadata().getClassName(), null);
        ReflectionUtils.doWithMethods(currentClass, method -> {
            EventSubscribe sub = method.getAnnotation(EventSubscribe.class);
            if (sub != null) {
                //处理bean注解
                String componentName = getComponentName(method);
                String event = getEvent((ParameterizedType) method.getGenericReturnType());
                processSubscribe(functionInfo, event, componentName, sub);
            }

            EventPublish pub = method.getAnnotation(EventPublish.class);
            if (method.getAnnotation(EventPublish.class) != null) {
                //处理bean注解
                String componentName = getComponentName(method);
                String event = getEvent((ParameterizedType) method.getGenericReturnType());
                processPublish(functionInfo, event, componentName, pub);
            }
        });
    }

    private String getComponentName(java.lang.reflect.Method method) {
        String componentName = null;
        Bean bean = method.getAnnotation(Bean.class);
        if (bean != null) {
            String[] componentNameArray = bean.value();
            for (String compName : componentNameArray) {
                componentName = compName;
            }
        }
        if (org.apache.commons.lang3.StringUtils.isBlank(componentName)) {
            componentName = method.getName();
        }
        return componentName;
    }

    private void processPublish(Map<String, FunctionInfo> functionInfo, String event, String funName, EventPublish pub) {
        FunctionInfo info = new FunctionInfo();
        info.setType(FunType.OUT);
        info.setFunName(funName);
        info.setEvent(event);
        BindingProperties bindingProperties = new BindingProperties();
        bindingProperties.setDestination(Constant.EVENTBUS);
        bindingProperties.setBinder(pub.binder());
        info.setBindingProperties(bindingProperties);
        info.setBinder(pub.binder());
        info.setBindersType(getBinderRelation(pub.binder()));
        functionInfo.put(funName + Constant.OUT_SUFFIX, info);
    }

    private void processSubscribe(Map<String, FunctionInfo> functionInfo, String event, String funName, EventSubscribe sub) {
        FunctionInfo info = new FunctionInfo();
        info.setType(FunType.IN);
        info.setFunName(funName);
        info.setEvent(event);
        info.setConsumer(sub.consumer());
        BindingProperties bindingProperties = new BindingProperties();
        String anonymousGroup = sub.anonymousGroup();
        info.setAnonymousGroup(anonymousGroup);
        if (StringUtils.isBlank(anonymousGroup)) {
            bindingProperties.setGroup(sub.group());
        }
        bindingProperties.setDestination(Constant.EVENTBUS);
        bindingProperties.setBinder(sub.binder());
        ConsumerProperties consumerProperties = new ConsumerProperties();
        consumerProperties.setConcurrency(sub.consumer());
        bindingProperties.setConsumer(consumerProperties);
        info.setBindingProperties(bindingProperties);
        info.setBinder(sub.binder());
        info.setBindersType(getBinderRelation(sub.binder()));
        functionInfo.put(funName + Constant.IN_SUFFIX, info);
    }

    private String getEvent(ParameterizedType genericReturnType) {
        Type[] actualTypeArguments = genericReturnType.getActualTypeArguments();
        String event = null;
        for (Type type : actualTypeArguments) {
            Class<?> clazz = (Class<?>) type;
            event = clazz.getName();
        }
        return event;
    }

    private boolean isEnabled() {

        return getEnvironment().getProperty(ConditionalOnEventBusEnabled.EVENT_BUS_ENABLED, Boolean.class, Boolean.TRUE);
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }

    public Environment getEnvironment() {
        return environment;
    }
}
